package pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.quickshapes
{
	import flash.geom.Point;
	
	import pl.ydp.components.mathcell.mathrecognizer.engine.Parameters;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.Angle;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.AngleTrend;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.EuclideanVector;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.classification.CurveType;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.classification.PrimitiveGeneralType;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.PrimitiveSubstance;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.features.FeaturesCross;
	
	public class QuickShape
	{
		public function QuickShape(vectors:Array, task:Number = -1) {
			
			if (task == -1){
				task = QuickShapeTask.ALL;
			}
			
			const MAX_LOOP_CLOSURE_DISTANCE:Number = Parameters.MAX_CONNECTION_DISTANCE*1.5;
			
			const PROCESS_LOOPS:Boolean = false;
			
			type = QuickShapeType.UNKNOWN;
			
			if (vectors.length > Parameters.LOOP_MIN_LENGTH  &&  Point.distance(vectors[0].point, vectors[vectors.length-1].point) < MAX_LOOP_CLOSURE_DISTANCE*(3/4)){
			
				if ((PROCESS_LOOPS  ||  task != QuickShapeTask.ALL)  &&  (task & QuickShapeTask.LOOP)  &&  checkLoop(vectors)){
					type = QuickShapeType.LOOP;
					substance = new PrimitiveSubstance(0, PrimitiveGeneralType.LOOP, findCurveType(vectors, 6), vectors.length-1, 0);
				} else if ((task & QuickShapeTask.FLOURISH)  &&  checkFlourish(vectors)){
					type = QuickShapeType.FLOURISH;
					substance = new PrimitiveSubstance(0, PrimitiveGeneralType.FLOURISH, CurveType.NONE, vectors.length-1, 0);
				}
			}
			
		}
		
		//------------------------------------------------------------------
						
		private const CROSSING_POSITIVE:Number = 1;
		private const CROSSING_NEGATIVE:Number = -1;
		
		public var type:Number;
		public var substance:PrimitiveSubstance;
		
		
		//------------------------------------------------------------------
		
		protected function checkLoop(vectors:Array):Boolean{
			
			var result:Boolean = false;	
			
			var crossings:Array;
			var axe:Object;
			var startIndex:Number = 0;
			
			var beginIndex:Number = Math.round(Parameters.MAX_CONNECTION_DISTANCE / Parameters.INTERP_VECTOR_LENGTH);
			var endIndex:Number = Math.round(vectors.length - Parameters.MAX_CONNECTION_DISTANCE / Parameters.INTERP_VECTOR_LENGTH);
						
			if (beginIndex > endIndex - 4){
				return false;
			}
			
			axe = findAxe(vectors,startIndex);
			
			if (Angle.differenceAbs(0,axe.angle) < AngleTrend.ANGLE_22_5){
				return false;
			}
			
			crossings = FeaturesCross.findSelfCrossings(vectors, -1, true);
			
			//crossings = findCrossings(vectors,startIndex, axe);
			
	
			return (crossings.length == 0  &&  axe.length > Parameters.LOOP_MIN_DIAMETER);
		}
		
		//------------------------------------------------------------------
		
		protected function checkFlourish(vectors:Array):Boolean{
			
			var result:Boolean = false;	
			
			var startIndex:Number;
						
			var beginIndex:Number = Math.round(Parameters.MAX_CONNECTION_DISTANCE / Parameters.INTERP_VECTOR_LENGTH);
			var endIndex:Number = Math.round(vectors.length - Parameters.MAX_CONNECTION_DISTANCE / Parameters.INTERP_VECTOR_LENGTH);
			
			if (beginIndex > endIndex - 4){
				return false;
			}
			
			// FIND FOR BEGIN
			
			startIndex = 0;
			
			if (findTwoCrossings(vectors, startIndex, true) ){
				return true;
			}
			
			// FIND FOR END
			 			
			startIndex = vectors.length-1;
			
			if (findTwoCrossings(vectors, startIndex, true) ){
				return true;
			}
			
			// FIND FOR TOP MOST VECTOR
			 			
			startIndex = findTopMostVectorIndex(vectors, false);
			
			if (findTwoCrossings(vectors, startIndex, true) ){
				return true;
			}
			
			
			// FIND FOR TOP MOST CENTERED VECTOR
			 			
			startIndex = findTopMostVectorIndex(vectors, true);
			
			if (findTwoCrossings(vectors, startIndex, true) ){
				return true;
			}
			
			return false;
		}
		
		//------------------------------------------------------------------
		
		private function findTwoCrossings(vectors:Array, startIndex:Number, centered:Boolean):Boolean{ 
			
			var crossings:Array;
			var axe:Object;
						
			axe = findAxe(vectors,startIndex, true);
			
			if (Angle.differenceAbs(0,axe.angle) < AngleTrend.ANGLE_22_5){
				return false;
			}
			
			crossings = findCrossings(vectors,startIndex, axe);
			
			if (crossings.length == 2){
				if (crossings[0] == crossings[1]){
					return true;
				}
			} 
			
			return false;
		}
		
		//------------------------------------------------------------------
		
		private function findTopMostVectorIndex(vectors:Array, centered:Boolean = false):Number{
			
			var value:Number = Infinity;
			var index:Number;
			var v:Number;
			var centerX:Number;
			
			if (centered){
				centerX = findCenterX(vectors);
			
				for (v = 0 ; v < vectors.length ; v ++){
					if (vectors[v].point.y + Math.abs(vectors[v].point.x-centerX) < value){
						value = vectors[v].point.y + Math.abs(vectors[v].point.x-centerX);
						index = v;
					}
				}
				
			} else {
				
				for (v = 0 ; v < vectors.length ; v ++){
					if (vectors[v].point.y < value){
						value = vectors[v].point.y;
						index = v;
					}
				}
			}
			
			return index;
		}
		
		//------------------------------------------------------------------
		
		private function findAxe(vectors:Array, startIndex:Number = 0, centered:Boolean = false):Object{
						
			var testDist:Number, v:Number;
			var maxDist:Number = 0;
			var maxDistAngle:Number;
			var maxDistIndex:Number;
			
			var beginIndex:Number = Math.round(Parameters.MAX_CONNECTION_DISTANCE / Parameters.INTERP_VECTOR_LENGTH);
			var endIndex:Number = Math.round(vectors.length - Parameters.MAX_CONNECTION_DISTANCE / Parameters.INTERP_VECTOR_LENGTH);
			
			if (beginIndex > endIndex - 4){
				return false;
			}
			
			var centerX:Number = findCenterX(vectors);
			
			for (v = beginIndex ; v < endIndex ; v += 1){
				if (!centered){
					testDist = Point.distance(vectors[startIndex].point, vectors[v].point);
				} else {
					testDist = Point.distance(vectors[startIndex].point, vectors[v].point) - Math.abs(vectors[v].point.x-centerX);
				}
				if (testDist > maxDist){
					maxDist = testDist;
					maxDistIndex = v;
				}
			}
			
			maxDistAngle = Angle.points2degrees(vectors[startIndex].point, vectors[maxDistIndex].point);			
			
			return {angle: maxDistAngle, index: maxDistIndex, length: maxDist};
				
		}
		
		//------------------------------------------------------------------
		
		private function findCrossings(vectors:Array, axeStartIndex:Number, axe:Object):Array{
			
			var output:Array = new Array();
			
			var tmpAngle1:Number;
			var tmpAngle2:Number;
			var v:Number;
			
			var beginIndex:Number = Math.round(Parameters.MAX_CONNECTION_DISTANCE / Parameters.INTERP_VECTOR_LENGTH);
			var endIndex:Number = Math.round(vectors.length - Parameters.MAX_CONNECTION_DISTANCE / Parameters.INTERP_VECTOR_LENGTH);
			
			if (axeStartIndex < vectors.length/2){
				beginIndex += axeStartIndex;
			} else {
				endIndex -= (vectors.length - axeStartIndex);
			}	
			
			for (v = beginIndex ; v < endIndex - 3 ; v += 2){
				tmpAngle1 = Angle.points2degrees(vectors[axeStartIndex].point, vectors[v].point);
				tmpAngle2 = Angle.points2degrees(vectors[axeStartIndex].point, vectors[v+3].point);		
				
				if ( Math.abs(tmpAngle1-tmpAngle2) < AngleTrend.ANGLE_22_5  &&  Math.abs(v+1 - axe.index) > 4){
					
					if (tmpAngle1 < axe.angle  &&  tmpAngle2 > axe.angle){
						output.push(CROSSING_NEGATIVE);
						v += 4;
					} else if (tmpAngle1 > axe.angle  &&  tmpAngle2 < axe.angle){
						output.push(CROSSING_POSITIVE);
						v += 4;
					}
					
				} //  end if
				
			} //  end for (v...
	
			
			return output;			
		}
			
		//------------------------------------------------------------------
		
		private function findCurveType(vectors:Array, elements:Number):Number{
			
			if (vectors == null){
				return CurveType.NONE;
			}
			if (vectors.length < elements){
				return CurveType.NONE;
			}
			if (elements < 2){
				return CurveType.NONE;
			}
			
			
			if (elements % 2 == 1 ){
				elements ++;
			}
			
			var angleDiff:Number;
			var angle0:Number, angle1:Number;
			
			angle0 = Angle.points2degrees( 	vectors[Math.round((elements/2-1)/elements*vectors.length)].point, 
											vectors[Math.round((elements/2)/elements*vectors.length)].point );

			angle1 = Angle.points2degrees( 	vectors[Math.round((elements/2)/elements*vectors.length)].point, 
											vectors[Math.round((elements/2+1)/elements*vectors.length)].point );
			
			angleDiff = Angle.difference( angle0, angle1 );
			
			if (angleDiff > 0){
				return CurveType.POSITIVE;
			} else if (angleDiff < 0){
				return CurveType.NEGATIVE;
			}
			
			return CurveType.NONE;
			
			
		}
		
		//------------------------------------------------------------------
		
		private function findCenterX(vectors:Array):Number{

			var left:Number = Infinity;
			var right:Number = -Infinity;
			var centerX:Number;

			for each (var vec:EuclideanVector in vectors){
				if (vec.point.x > right){
					right = vec.point.x;
				} else if (vec.point.x < left){
					left = vec.point.x;
				}
			}

			
			centerX = left + (right - left)/2;
			
			return centerX;			
			
		}
		
	}
}